#include <algorithm>
#include <stdexcept>

#include "tinyxml/tinyxml.h"

#include "DeminaAnimation.hpp"
#include "AssetManager.hpp"
#include "Path.hpp"
#include "SpriteBatch.hpp"
#include "Math.hpp"
#include "Texture.hpp"
#include "XmlUtil.hpp"

namespace fge
{

DeminaAnimation::DeminaAnimation(const std::string &filename, AssetManager *assetManager)
	: textureOwner(false)
{
	TiXmlDocument xml(assetManager->GetAssetPath() + filename);
	int firstBoneCount = -1;

	std::string directory = Path::GetDirectoryName(filename);

	bool isLoaded = xml.LoadFile();
	if(isLoaded)
	{
		TiXmlElement *animElement = xml.FirstChildElement("Animation");

		frameRate = StringToNumber<int>(animElement->FirstChildElement("FrameRate")->FirstChild()->Value());
		float invFrameRate = 1.0f / frameRate;

		loopFrame = StringToNumber<int>(animElement->FirstChildElement("LoopFrame")->FirstChild()->Value());
		loopTime = invFrameRate * loopFrame;

		int textureCount = GetChildCount(animElement, "Texture");
		TiXmlNode *textureNode = animElement->FirstChild("Texture");
		for(int i=0; i<textureCount; i++)
		{
			std::string texturePath = Path::CombinePaths(directory, textureNode->FirstChild()->Value());

			Texture *texture = assetManager->LoadTexture(texturePath);
			textures.push_back(texture);
			textureNode = textureNode->NextSibling("Texture");
		}

		int keyCount = GetChildCount(animElement, "Keyframe");
		TiXmlElement *keyElement = animElement->FirstChildElement("Keyframe");
		for(int i=0; i<keyCount; i++)
		{
			Keyframe keyframe;

			keyframe.VerticalFlip = strcmp(keyElement->Attribute("vflip"), "True") == 0;
			keyframe.HorizontalFlip = strcmp(keyElement->Attribute("hflip"), "True") == 0;
			std::string test = keyElement->Attribute("frame");
			keyframe.FrameNumber = StringToNumber<int>(keyElement->Attribute("frame"));
			keyframe.FrameTime = invFrameRate * keyframe.FrameNumber;
			keyframe.FrameTrigger = keyElement->Attribute("trigger");

			int boneCount = GetChildCount(keyElement, "Bone");

			if(firstBoneCount != -1 && boneCount != firstBoneCount)
				throw std::exception();
			else
				firstBoneCount = boneCount;

			TiXmlElement *boneElement = keyElement->FirstChildElement("Bone");
			for(int j=0; j<boneCount; j++)
			{
				Bone bone;

				bone.Name = boneElement->Attribute("name");
				bone.Visible = strcmp(boneElement->FirstChild("Hidden")->FirstChild()->Value(), "False") == 0;
				bone.VerticalFlip = strcmp(boneElement->FirstChild("TextureFlipVertical")->FirstChild()->Value(), "True") == 0;
				bone.HorizontalFlip = strcmp(boneElement->FirstChild("TextureFlipHorizontal")->FirstChild()->Value(), "True") == 0;
				bone.TextureIndex = StringToNumber<int>(boneElement->FirstChild("TextureIndex")->FirstChild()->Value());
				bone.ParentIndex = StringToNumber<int>(boneElement->FirstChild("ParentIndex")->FirstChild()->Value());
				bone.Position.X() = StringToNumber<float>(boneElement->FirstChild("Position")->FirstChild("X")->FirstChild()->Value());
				bone.Position.Y() = StringToNumber<float>(boneElement->FirstChild("Position")->FirstChild("Y")->FirstChild()->Value());
				bone.Rotation = StringToNumber<float>(boneElement->FirstChild("Rotation")->FirstChild()->Value());

				keyframe.Bones.push_back(bone);
				boneElement = boneElement->NextSiblingElement("Bone");
			}

			keyframes.push_back(keyframe);
			keyElement = keyElement->NextSiblingElement("Keyframe");
		}
	}
}

DeminaAnimation::~DeminaAnimation()
{
	if(textureOwner)
	{
		int textureCount = (int)textures.size();
		for(int i=0; i<textureCount; i++)
		{
			delete textures[i];
		}
	}
}

DeminaAnimation* DeminaAnimation::FromFile(const std::string &filename)
{
	DeminaAnimation *animation = new DeminaAnimation();
	animation->textureOwner = true;

	TiXmlDocument xml(filename);
	int firstBoneCount = -1;

	std::string directory = Path::GetDirectoryName(filename);

	bool isLoaded = xml.LoadFile();
	if(isLoaded)
	{
		TiXmlElement *animElement = xml.FirstChildElement("Animation");

		animation->frameRate = StringToNumber<int>(animElement->FirstChildElement("FrameRate")->FirstChild()->Value());
		float invFrameRate = 1.0f / animation->frameRate;

		animation->loopFrame = StringToNumber<int>(animElement->FirstChildElement("LoopFrame")->FirstChild()->Value());
		animation->loopTime = invFrameRate * animation->loopFrame;

		int textureCount = GetChildCount(animElement, "Texture");
		TiXmlNode *textureNode = animElement->FirstChild("Texture");
		for(int i=0; i<textureCount; i++)
		{
			std::string texturePath = Path::CombinePaths(directory, textureNode->FirstChild()->Value());

			Texture *texture = Texture::FromFile(texturePath);
			animation->textures.push_back(texture);
			textureNode = textureNode->NextSibling("Texture");
		}

		int keyCount = GetChildCount(animElement, "Keyframe");
		TiXmlElement *keyElement = animElement->FirstChildElement("Keyframe");
		for(int i=0; i<keyCount; i++)
		{
			Keyframe keyframe;

			keyframe.VerticalFlip = strcmp(keyElement->Attribute("vflip"), "True") == 0;
			keyframe.HorizontalFlip = strcmp(keyElement->Attribute("hflip"), "True") == 0;
			std::string test = keyElement->Attribute("frame");
			keyframe.FrameNumber = StringToNumber<int>(keyElement->Attribute("frame"));
			keyframe.FrameTime = invFrameRate * keyframe.FrameNumber;
			keyframe.FrameTrigger = keyElement->Attribute("trigger");

			int boneCount = GetChildCount(keyElement, "Bone");

			if(firstBoneCount != -1 && boneCount != firstBoneCount)
				throw std::exception();
			else
				firstBoneCount = boneCount;

			TiXmlElement *boneElement = keyElement->FirstChildElement("Bone");
			for(int j=0; j<boneCount; j++)
			{
				Bone bone;

				bone.Name = boneElement->Attribute("name");
				bone.Visible = strcmp(boneElement->FirstChild("Hidden")->FirstChild()->Value(), "False") == 0;
				bone.VerticalFlip = strcmp(boneElement->FirstChild("TextureFlipVertical")->FirstChild()->Value(), "True") == 0;
				bone.HorizontalFlip = strcmp(boneElement->FirstChild("TextureFlipHorizontal")->FirstChild()->Value(), "True") == 0;
				bone.TextureIndex = StringToNumber<int>(boneElement->FirstChild("TextureIndex")->FirstChild()->Value());
				bone.ParentIndex = StringToNumber<int>(boneElement->FirstChild("ParentIndex")->FirstChild()->Value());
				bone.Position.X() = StringToNumber<float>(boneElement->FirstChild("Position")->FirstChild("X")->FirstChild()->Value());
				bone.Position.Y() = StringToNumber<float>(boneElement->FirstChild("Position")->FirstChild("Y")->FirstChild()->Value());
				bone.Rotation = StringToNumber<float>(boneElement->FirstChild("Rotation")->FirstChild()->Value());

				keyframe.Bones.push_back(bone);
				boneElement = boneElement->NextSiblingElement("Bone");
			}

			animation->keyframes.push_back(keyframe);
			keyElement = keyElement->NextSiblingElement("Keyframe");
		}
	}

	return animation;
}

DeminaAnimationPlayer::DeminaAnimationPlayer()
	: boneCount(-1), currentAnimation(NULL), transitioning(false)
{
}

DeminaAnimationPlayer::~DeminaAnimationPlayer()
{
}

void DeminaAnimationPlayer::InsertUpdateOrderIndex(int boneIndex)
{
	if(std::find(boneUpdateOrder.begin(), boneUpdateOrder.end(), boneIndex) != boneUpdateOrder.end())
		return;

	Bone &bone = currentAnimation->keyframes[0].Bones[boneIndex];
	if(bone.ParentIndex != -1)
		InsertUpdateOrderIndex(bone.ParentIndex);
	boneUpdateOrder.push_back(boneIndex);
}

void DeminaAnimationPlayer::UpdateBoneTransforms()
{
	Keyframe *lerpKeyframe;
	float t;
	float time = currentAnimationTime - currentAnimation->keyframes[currentKeyframeIndex].FrameTime;

	if(transitioning)
	{
		lerpKeyframe = &transitionAnimation->keyframes[0];
		t = transitionTime / transitionTotalTime;

		for(int i=0; i<boneCount; i++)
		{
			int currentBone = boneUpdateOrder[i];

			Bone &lerpBone = lerpKeyframe->Bones[currentBone];

			boneTransforms[currentBone].Rotation = Lerp(transitionStates[currentBone].Rotation, lerpBone.Rotation, t);
			boneTransforms[currentBone].Translation = Lerp(Vector2(transitionStates[currentBone].Translation.X(), transitionStates[currentBone].Translation.Y()), Vector2(lerpBone.Position.X(), lerpBone.Position.Y()), t);
			Matrix3 parentMatrix = currentAnimation->keyframes[currentKeyframeIndex].Bones[currentBone].ParentIndex == -1 ? Matrix3::IDENTITY : boneTransforms[currentAnimation->keyframes[currentKeyframeIndex].Bones[currentBone].ParentIndex].Transform;
			boneTransforms[currentBone].Transform =  parentMatrix * CreateTranslationMatrix(boneTransforms[currentBone].Translation.X(), boneTransforms[currentBone].Translation.Y()) * CreateRotationMatrix(boneTransforms[currentBone].Rotation);
		}

		return;
	}
	
	if (currentKeyframeIndex == currentAnimation->keyframes.size() - 1)
	{
		lerpKeyframe = &currentAnimation->keyframes[0];
	
		if (currentAnimation->loopFrame != -1)
			t = time / (currentAnimation->loopTime - currentAnimation->keyframes[currentKeyframeIndex].FrameTime);
		else
			t = 0;
	}
	else
	{
		lerpKeyframe = &currentAnimation->keyframes[currentKeyframeIndex + 1];
		t = time / (lerpKeyframe->FrameTime - currentAnimation->keyframes[currentKeyframeIndex].FrameTime);
	}

	for(int i=0; i<boneCount; i++)
	{
		int currentBone = boneUpdateOrder[i];

		Bone &bone = currentAnimation->keyframes[currentKeyframeIndex].Bones[currentBone];
		Bone &lerpBone = lerpKeyframe->Bones[currentBone];

		boneTransforms[currentBone].Rotation = Lerp(bone.Rotation, lerpBone.Rotation, t);
		boneTransforms[currentBone].Translation = Lerp(Vector2(bone.Position.X(), bone.Position.Y()), Vector2(lerpBone.Position.X(), lerpBone.Position.Y()), t);
		Matrix3 parentMatrix = currentAnimation->keyframes[currentKeyframeIndex].Bones[currentBone].ParentIndex == -1 ? Matrix3::IDENTITY : boneTransforms[currentAnimation->keyframes[currentKeyframeIndex].Bones[currentBone].ParentIndex].Transform;
		boneTransforms[currentBone].Transform =  parentMatrix * CreateTranslationMatrix(boneTransforms[currentBone].Translation.X(), boneTransforms[currentBone].Translation.Y()) * CreateRotationMatrix(boneTransforms[currentBone].Rotation);
	}
}

void DeminaAnimationPlayer::AddAnimation(const std::string &name, DeminaAnimation *deminaAnimation)
{
	if(boneCount != -1 && boneCount != deminaAnimation->keyframes[0].Bones.size())
		throw std::runtime_error(name + " invalid bone count");

	animations[name] = deminaAnimation;

	if(boneCount == -1)
	{
		boneCount = deminaAnimation->keyframes[0].Bones.size();
		currentAnimation = deminaAnimation;
		currentAnimationName = name;
		currentAnimationTime = 0;
		currentKeyframeIndex = 0;

		boneTransforms.clear();
		boneUpdateOrder.clear();

		BoneTransform bt;
		bt.Rotation = 0;
		bt.Translation = Vector2::ZERO;
		bt.Transform = Matrix3::IDENTITY;

		BoneTransitionState bts;
		bts.Rotation = 0;
		bts.Translation = Vector2::ZERO;

		for(int i=0; i<boneCount; i++)
		{
			boneTransforms.push_back(bt);
			InsertUpdateOrderIndex(i);
			transitionStates.push_back(bts);
		}
	}
}

AnimationEvent DeminaAnimationPlayer::Update(float deltaSeconds)
{
	AnimationEvent animationEvent = { false, "" };

	if (currentAnimation == NULL)
		return animationEvent;

	int startKeyframeIndex = currentKeyframeIndex;

	if (transitioning)
	{
		transitionTime += deltaSeconds;

		if (transitionTime > transitionTotalTime)
		{
			transitioning = false;

			currentAnimation = transitionAnimation;
			currentAnimationName = transitionAnimationName;
			// note: the next line is more correct, but setting to zero dodges errors from long delays
			//currentAnimationTime = transitionTime - transitionTotalTime;
			currentAnimationTime = 0;
			currentKeyframeIndex = 0;
		}
	}
	else
	{
		bool reachedEnd = false;

		currentAnimationTime += deltaSeconds;

		if (currentKeyframeIndex == currentAnimation->keyframes.size() - 1)
		{
			if (currentAnimation->loopFrame != -1)
			{
				if (currentAnimationTime > currentAnimation->loopTime)
				{
					currentAnimationTime -= currentAnimation->loopTime;
					currentKeyframeIndex = 0;
				}
			}
			else
			{
				currentAnimationTime = currentAnimation->keyframes[currentKeyframeIndex].FrameTime;
				reachedEnd = true;
			}
		}
		else
		{
			if (currentAnimationTime > currentAnimation->keyframes[currentKeyframeIndex + 1].FrameTime)
			{
				// note: this line introduces a slight timing error, but dodges errors caused by very long delays
				currentAnimationTime = currentAnimation->keyframes[currentKeyframeIndex + 1].FrameTime;
				currentKeyframeIndex++;
			}
		}

		animationEvent.AnimationComplete = reachedEnd;
	}

	UpdateBoneTransforms();

	// Set the frame trigger, could be setting it to "" if no event, which is fine
	if (currentKeyframeIndex != startKeyframeIndex)
		animationEvent.AnimationTrigger = currentAnimation->keyframes[currentKeyframeIndex].FrameTrigger;

	return animationEvent;
}

AnimationEvent DeminaAnimationPlayer::SetTime(float seconds)
{
	AnimationEvent animationEvent = { false, "" };

	if (currentAnimation == NULL)
		return animationEvent;

	if (transitioning)
	{
		transitioning = false;

		currentAnimation = transitionAnimation;
		currentAnimationName = transitionAnimationName;
	}

	currentAnimationTime = seconds;
	currentKeyframeIndex = 0;

	int keyframeCount = currentAnimation->keyframes.size();
	for(int i=0; i<keyframeCount; i++)
	{
		if(currentAnimation->keyframes[i].FrameTime > seconds)
			break;
		currentKeyframeIndex = i;
	}

	UpdateBoneTransforms();

	animationEvent.AnimationTrigger = currentAnimation->keyframes[currentKeyframeIndex].FrameTrigger;

	return animationEvent;
}


void DeminaAnimationPlayer::Draw(SpriteBatch *spriteBatch, const Vector2 &position, float rotation, const Vector2 &scale, const Color &tintColor, bool flipHorizontal, bool flipVertical)
{
	if(currentAnimation == NULL)
		return;

	Matrix3 scaleMatrix = CreateScaleMatrix(scale.X(), scale.Y());

	if(bxor(flipHorizontal, currentAnimation->keyframes[currentKeyframeIndex].HorizontalFlip))
		scaleMatrix = CreateScaleMatrix(-1, 1);
	if(bxor(flipVertical, currentAnimation->keyframes[currentKeyframeIndex].VerticalFlip))
		scaleMatrix = CreateScaleMatrix(1, -1) * scaleMatrix;

	for(int i=0; i<boneCount; i++)
	{
		bool bvf = currentAnimation->keyframes[currentKeyframeIndex].Bones[i].VerticalFlip;
		bool bhf = currentAnimation->keyframes[currentKeyframeIndex].Bones[i].HorizontalFlip;

		spriteBatch->Draw(currentAnimation->textures[currentAnimation->keyframes[currentKeyframeIndex].Bones[i].TextureIndex],
			Vector2::ZERO, NULL, Vector2(0.5f, 0.5f), 0, tintColor, Vector2(bhf ? -1.0f : 1.0f, bvf ? -1.0f : 1.0f),
			CreateTranslationMatrix(position.X(), position.Y()) * CreateRotationMatrix(rotation) * scaleMatrix * boneTransforms[i].Transform);
	}
}

void DeminaAnimationPlayer::StartAnimation(const std::string &name, bool allowRestart)
{
	if (currentAnimationName != name || allowRestart)
	{
		transitioning = false;

		currentAnimationName = name;
		currentAnimation = animations[name];
		currentAnimationTime = 0;
		currentKeyframeIndex = 0;
	
		Update(0);
	}
}

void DeminaAnimationPlayer::TransitionAnimation(const std::string &name, float transitionDelay)
{
	for(int i=0; i<boneCount; i++)
	{
		transitionStates[i].Rotation = boneTransforms[i].Rotation;
		transitionStates[i].Translation = boneTransforms[i].Translation;
	}

	transitioning = true;
	transitionTime = 0;
	transitionTotalTime = transitionDelay;
	transitionAnimationName = name;
	transitionAnimation = animations[name];
}

}


